﻿// IPC Library
// Interprocess Communication Library
// Copyright(C)2008 Bill Tudor
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using Microsoft.Win32.SafeHandles;

namespace IPCLibrary
{
	/// <summary>
	/// Base implementation of mailslot.
	/// </summary>
	/// <seealso cref="IPCLibrary.MailslotClient"/>
	/// <seealso cref="IPCLibrary.Mailslot"/>
	public abstract class MailslotBase : IDisposable
	{
		private string _name;
		private string _scopeName;
		private byte[] _buffer;
		private const string _DEFAULT_SCOPE = ".";
		private bool _disposed;

		private MailslotSafeHandle _mailslotHandle;
		private SafeFileHandle _fileHandle;
		/// <summary>
		/// Full name of the mailslot.
		/// </summary>
		public string FullName
		{
			get { return BuildFullName(_scopeName, _name);}
		}
		/// <summary>
		/// Name of the mailslot.
		/// </summary>
		public string Name
		{
			get { return _name; }
		}
		/// <summary>
		/// Scope of the mailslot.
		/// </summary>
		public string Scope
		{
			get { return _scopeName; }
		}

		private MailslotBase()
		{
			_name = String.Empty;
			_scopeName = String.Empty;
			_buffer = new byte[NativeMethods.MAX_MAILSLOT_SIZE];
		}
		/// <summary>
		/// Creates a mailslot.
		/// </summary>
		/// <param name="mailslotName">Mailslot name.</param>
		/// <remarks>
		/// The scope of this mailslot will be the local machine, i.e., ".".
		/// </remarks>
		protected MailslotBase(string mailslotName)
			: this(_DEFAULT_SCOPE, mailslotName) { }
		/// <summary>
		/// Creates a mailslot.
		/// </summary>
		/// <param name="mailslotScope">Mailslot scope.</param>
		/// <param name="mailslotName">Mailslot name.</param>
		/// <remarks>
		/// The mailslot scope can be a hostname, a domain name, or one of two
		/// special characters: use "*" for the current system's primary domain, or
		/// use "." to represent the current computer.
		/// <para></para>
		/// <para>For IPC between processes on the same machine, use ".".</para>
		/// <para>For IPC between machines in the current domain, use "*".</para>
		/// <para>For IPC between the current host and a specific mailslot server, use the hostname
		/// of the remote server.</para>
		/// <para>For IPC between the current host and any number of servers on any computer in the
		/// host's domain, use either the "*" special name or the domain name".</para>
		/// </remarks>
		protected MailslotBase(string mailslotScope, string mailslotName)
			:this()
		{
			_scopeName = mailslotScope;
			_name = mailslotName;
		}
		/// <summary>
		/// Close the mailslot.
		/// </summary>
		public void Close()
		{
			if (_mailslotHandle != null && !_mailslotHandle.IsInvalid)
			{
				_mailslotHandle.Close();
			}
			if (_fileHandle != null && !_fileHandle.IsInvalid)
			{
				_fileHandle.Close();
			}
		}
		/// <summary>
		/// Create server mailsot.
		/// </summary>
		protected void CreateServer()
		{
			_mailslotHandle = NativeMethods.CreateMailslot(FullName, 0, NativeMethods.MAILSLOT_WAIT_FOREVER, IntPtr.Zero);
			if (_mailslotHandle.IsInvalid)
			{
				throw IPCException.FromLastError("Unable to create Mailslot server.", "CreateMailslot");
			}
		}
		/// <summary>
		/// Open client mailslot.
		/// </summary>
		protected void OpenClient()
		{
			if (_fileHandle == null || _fileHandle.IsInvalid)
			{
				_fileHandle = NativeMethods.CreateFile(FullName, NativeMethods.FILE_GENERIC_WRITE, NativeMethods.FILE_SHARE_READ, IntPtr.Zero, NativeMethods.OPEN_EXISTING, NativeMethods.FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);
				if (_fileHandle.IsInvalid)
				{
					throw IPCException.FromLastError("Unable to open the mailslot.", "CreateFile");
				}
			}
		}
		/// <summary>
		/// Read data from the mailslot.
		/// </summary>
		/// <returns>Date read.</returns>
		protected byte[] ReadBytes()
		{
			int bytesRead = 0;
			if (_mailslotHandle != null && !_mailslotHandle.IsInvalid)
			{
				if (NativeMethods.ReadFile(_mailslotHandle, _buffer, _buffer.Length, out bytesRead, IntPtr.Zero))
				{
					if (bytesRead <= _buffer.Length)
					{
						byte[] result = new byte[bytesRead];
						if (bytesRead > 0)
						{
							Array.Copy(_buffer, result, bytesRead);
						}
						return result;
					}
				}
			}
			throw IPCException.FromLastError("Unable to read data from the mailslot.", "ReadFile");
		}
		/// <summary>
		/// Write data to the mailslot.
		/// </summary>
		/// <param name="data">Data to write.</param>
		/// <param name="dataLength">Length of the data.</param>
		/// <returns></returns>
		protected int WriteBytes(byte[] data, int dataLength)
		{
			int bytesWritten;
			if (NativeMethods.WriteFile(_fileHandle, data, dataLength, out bytesWritten, IntPtr.Zero))
			{
				return bytesWritten;
			}
			throw IPCException.FromLastError("Unable to write data to the mailslot.", "WriteFile");
		}
		/// <summary>
		/// Build full mailslot name.
		/// </summary>
		/// <param name="scopeName">Scope name.</param>
		/// <param name="name">mailsot name.</param>
		/// <returns>Full mailslot name.</returns>
		protected static string BuildFullName(string scopeName, string name)
		{
			return String.Format(CultureInfo.CurrentCulture, @"\\{0}\mailslot\{1}", scopeName, name);
		}
		/// <summary>
		/// Retrieve mailslot information.
		/// </summary>
		/// <param name="maxSize">Maximum mailslot message size.</param>
		/// <param name="nextSize">Size of the next available message.</param>
		/// <param name="count">Number of messages waiting.</param>
		/// <param name="timeout">Timeout period.</param>
		protected bool GetMailslotInfo(ref int maxSize, ref int nextSize, ref int count, ref int timeout)
		{
			bool result = false;
			if (_mailslotHandle != null && !_mailslotHandle.IsInvalid)
			{
				if (!NativeMethods.GetMailslotInfo(_mailslotHandle, ref maxSize, ref nextSize, ref count, ref timeout))
				{
					throw IPCException.FromLastError("Unable to update mailslot information.", "GetInfo");
				}
				result = true;
			}
			return result;
		}

		#region IDisposable Members

		/// <summary>
		/// Release unmanaged resources. 
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		/// <summary>
		/// Release unmanaged resources.
		/// </summary>
		/// <param name="disposing">True if disposing.</param>
		protected virtual void Dispose(bool disposing)
		{
			if (!this._disposed)
			{
				if (disposing)
				{
					// Dispose managed resources here.
				}
				Close();
			}
			_disposed = true;
		}

		#endregion

	}
}
